using System;
using System.IO;
using MonoTouch.Foundation;

namespace Ro.Norc.ServiceStackClient
{
	internal class ServiceTransportNSUrlConnectionDelegate : NSUrlConnectionDelegate
	{
		/// <summary>
		/// The transport instance which receives notifications from this delegate
		/// </summary>
		private ServiceTransportNSUrlConnection _transport = null;
		
		/// <summary>
		/// The HTTP status code of the current request
		/// </summary>
		private int _statusCode = 0;
		
		/// <summary>
		/// The content type of the received content
		/// </summary>
		private string _contentType = null;
		
		/// <summary>
		/// The content length of the received content
		/// </summary>
		private long _contentLength = 0;
		
		/// <summary>
		/// Local buffer for the incoming data
		/// </summary>
		private MemoryStream _buffer = null;
		
		/// <summary>
		/// The request identifier
		/// </summary>
		private string _requestId = null;
		
		/// <summary>
		/// Initializes a new ServiceTransportNSUrlConnectionDelegate instance
		/// </summary>
		/// <param name='transport'>
		/// The transport instance which receives notifications from this delegate
		/// </param>
		/// <exception cref='ArgumentNullException'>
		/// If the transport instance is null
		/// </exception>
		public ServiceTransportNSUrlConnectionDelegate (ServiceTransportNSUrlConnection transport)
		{
			if (transport == null) {
				throw new ArgumentNullException("transport");
			}
			_transport = transport;
			_buffer = new MemoryStream();
			_requestId = _transport.CurrentRequest.RequestId;
		}
		
		/// <summary>
		/// Called when a new data batch is sent to the server
		/// </summary>
		/// <param name="connection">
		/// The receiving connection
		/// </param>
		/// <param name="bytesWritten">
		/// The number of bytes writter
		/// </param>
		/// <param name="totalBytesWritten">
		/// The total number of bytes written
		/// </param>
		/// <param name="totalBytesExpectedToWrite">
		/// The total number of bytes expected to be written by the end of the upload phase
		/// </param>
		public override void SentBodyData (NSUrlConnection connection, int bytesWritten, int totalBytesWritten, int totalBytesExpectedToWrite)
		{
			if (totalBytesExpectedToWrite > 0) {
				_transport.OnUploadProgress(Math.Round((double)totalBytesWritten / totalBytesExpectedToWrite, 3));
			}
		}
		
		/// <summary>
		/// Called when some data is received
		/// </summary>
		/// <param name="connection">
		/// The connection that received the data
		/// </param>
		/// <param name="data">
		/// The received data
		/// </param>
		public override void ReceivedData (NSUrlConnection connection, NSData data)
		{
			for (int i = 0; i < data.Length; i ++) {
				_buffer.WriteByte(data[i]);
			}
			if (_contentLength > 0) {
				_transport.OnProgress(Math.Round((double)_buffer.Length / _contentLength, 3));
			}
		}
		
		/// <summary>
		/// Resets the current delegate instance
		/// </summary>
		private void Clear ()
		{
			if (_buffer.Length > 0) {
				_buffer.SetLength(0);
			}
			_statusCode = 0;
			_contentType = null;
			_contentLength = 0;
			_requestId = null;
		}
		
		/// <summary>
		/// Called when an error occurs
		/// </summary>
		/// <param name="connection">
		/// The connection that issued the error
		/// </param>
		/// <param name="error">
		/// The error that occured
		/// </param>
		public override void FailedWithError (NSUrlConnection connection, NSError error)
		{
			_buffer.SetLength(0);
			if (!_transport.IsRequestValid(_requestId)) {
				Clear();
				return;
			}
			_transport.OnResponse(new ServiceTransportResponse() {
				Error = error
			});
			Clear();
		}
		
		/// <summary>
		/// Called to check whether the client can authenticate using a given authentication method
		/// Currently, only server trust authentication and basic http authentication challenges are handled.
		/// </summary>
		/// <param name="connection">
		/// The receiving connection
		/// </param>
		/// <param name="protectionSpace">
		/// The protection space that specified the authentication method
		/// </param>
		/// <returns>
		/// True if the delegate supports the requested authentication method, False otherwise
		/// </returns>
		public override bool CanAuthenticateAgainstProtectionSpace (NSUrlConnection connection, NSUrlProtectionSpace protectionSpace)
		{
			if (!_transport.IsRequestValid(_requestId)) {
				Clear();
				return false;
			}

			if (protectionSpace.AuthenticationMethod == "NSURLAuthenticationMethodServerTrust" || 
				protectionSpace.AuthenticationMethod == "NSURLAuthenticationMethodHTTPBasic" || 
				protectionSpace.AuthenticationMethod == "NSURLAuthenticationMethodDefault") {
				return true;
			} else {
				return false;
			}
		}
		
		/// <summary>
		/// Called when the actual authentication challenge is received from the server.
		/// Currently, only server trust authentication and basic http authentication challenges are handled.
		/// </summary>
		/// <param name="connection">
		/// The receiving connection
		/// </param>
		/// <param name="challenge">
		/// The authentication challenge
		/// </param>
		public override void ReceivedAuthenticationChallenge (NSUrlConnection connection, NSUrlAuthenticationChallenge challenge)
		{
			NSUrlConnection _sender = challenge.Sender;
			ServiceBasicHttpAuthPrompt _prompt = null;
			NSUrlProtectionSpace _protectionSpace = null;

			if (!_transport.IsRequestValid(_requestId)) {
				Clear();
				return;
			}

			_protectionSpace = challenge.ProtectionSpace;
			if (_protectionSpace.AuthenticationMethod == "NSURLAuthenticationMethodHTTPBasic" ||
				_protectionSpace.AuthenticationMethod == "NSURLAuthenticationMethodDefault") {
				if (challenge.PreviousFailureCount == 0) {
					_prompt = new ServiceBasicHttpAuthPrompt();
					_transport.OnBasicHttpAuthenticationPrompt(_prompt);
					_sender.UseCredentials(NSUrlCredential.FromUserPasswordPersistance(_prompt.UserName, _prompt.Password, NSUrlCredentialPersistence.ForSession), challenge);
				} else {
					_sender.CancelAuthenticationChallenge(challenge);
				}
			} else if (_protectionSpace.AuthenticationMethod == "NSURLAuthenticationMethodServerTrust") {
				_sender.UseCredentials(NSUrlCredential.FromTrust(_protectionSpace.ServerTrust), challenge);
			} else {
				_sender.ContinueWithoutCredentialForAuthenticationChallenge(challenge);
			}
		}
		
		/// <summary>
		/// Called when the initial HTTP response is received (usually called right after the headers are read)
		/// </summary>
		/// <param name="connection">
		/// The receiving connection
		/// </param>
		/// <param name="response">
		/// The HTTP response
		/// </param>
		public override void ReceivedResponse (NSUrlConnection connection, NSUrlResponse response)
		{
			NSHttpUrlResponse _response = response as NSHttpUrlResponse;
			
			_buffer.SetLength(0);
			_statusCode = _response.StatusCode;
			_contentType = _response.MimeType;
			_contentLength = _response.ExpectedContentLength;
			
			if (!_transport.IsRequestValid(_requestId)) {
				Clear();
				return;
			}
			
			if (_transport.CurrentRequest.CallType.OneWay) {
				_transport.OnResponse(new ServiceTransportResponse {
					StatusCode = _statusCode,
					ContentType = _contentType
				});
				Clear();
			}
		}
		
		/// <summary>
		/// Called when the content is fully and successfully received
		/// </summary>
		/// <param name="connection">
		/// The receiving connection
		/// </param>
		public override void FinishedLoading (NSUrlConnection connection)
		{
			byte[] _responseBytes = null;
			
			if (!_transport.IsRequestValid(_requestId)) {
				Clear();
				return;
			}
			
			//check HTTP status code
			if (_statusCode > 0 && (_statusCode < 200 || _statusCode >= 300)) {
				_transport.OnResponse(new ServiceTransportResponse {
					Error = ServiceUtil.GetError(ServiceErrorCode.ERROR_LOAD_ERROR)
				});
				return;
			}
			
			_responseBytes = new byte[_buffer.Length];
			_buffer.Position = 0;
			_buffer.Read(_responseBytes, 0, _responseBytes.Length);
			_buffer.SetLength(0);
			
			//check that we have received some content
			if (_responseBytes.Length == 0) {
				_transport.OnResponse(new ServiceTransportResponse {
					Error = ServiceUtil.GetError(ServiceErrorCode.ERROR_CONTENT_MISSING)
				});
				return;
			}

			//response seems to be ok, at least at the transport level
			_transport.OnResponse(new ServiceTransportResponse {
				StatusCode = _statusCode,
				ContentType = _contentType,
				Body = _responseBytes
			});
			
			//we're done,clear
			Clear();
		}

		protected override void Dispose (bool disposing)
		{
			if (_transport != null) {
				_transport = null;
			}
			base.Dispose (disposing);
		}
	}
}